package top.codingfly.aliyun.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.codingfly.aliyun.entity.PlayAuth;
import top.codingfly.aliyun.entity.PlayInfo;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.SignatureException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 阿里云API工具类
 *
 * @author suchu
 * @date 2020/8/6
 */
public class AliyunUtils {
    private static Logger logger = LoggerFactory.getLogger(AliyunUtils.class);

    public static PlayInfo getPlayInfoRequestUrl(String aliUrl, String aliPlayAuth, String videoId) {
        return getVodPlayerInfo(aliUrl, "", aliPlayAuth, videoId, "");
    }

    public static void main(String[] args) {
        String rand = UUID.randomUUID().toString();
        String encryptRand = EncryptUtils.encryptRand(rand);
        String playAuth = "eyJTZWN1cml0eVRva2VuIjoiQ0FJU2lBTjFxNkZ0NUIyeWZTaklyNWYyUFlLRzN1b1k0NmljTkJlRmwwTm5aZXhoaVozYmt6ejJJSDFQZlhGdEFlMFp0LzArbVdsUTV2d2FscklxRXNRZkd4ZWRNWkVwdHNrSXJseitKb0hidk5ldTBic0hoWnY5MDZZZHdzR2lqcUhvZU96Y1lJNzMwWjdQQWdtMlEwWVJySkwrY1RLOUphYk1VL21nZ29KbWFkSTZSeFN4YVNFOGF2NWRPZ3BscnIwSVZ4elBNdnIvSFJQMnVtN1pIV3R1dEEwZTgzMTQ1ZmFRejlHaTZ4YlRpM2I5ek9FVXFPYVhKNFMvUGZGb05ZWnlTZjZvd093VUVxL2R5M3hvN3hGYjFhRjRpODRpL0N2YzdQMlFDRU5BK3dtbFB2dTJpOE5vSUYxV2E3UVdJWXRncmZQeGsrWjEySmJOa0lpbDVCdFJFZHR3ZUNuRldLR216c3krYjRIUEROc2ljcXZoTUhuZ3k4MkdNb0tQMHprcGVuVUdMZ2hIQ2JGRFF6MVNjVVYxRVd1RWNmYjdvZ21TUDFqOEZQZS92ZnRvZ2NZdi9UTEI1c0dYSWxXRGViS2QzQnNETjRVMEIwRlNiRU5OaERTOEt2SlpLbFVkTGdvL1Yrek5WL2xhYUJGUHRLWFdtaUgrV3lOcDAzVkxoZnI2YXVOcGJnUHIxVVFwTlJxQUFWaVhxbHJvQkJFckFvMXNQYzA4US96WU5HNVM4SnJSTng5YUZvODlCRTZPTnIrRlpGalF5a0xSN0t5UnBwNXNUUHJ2dFRQcWVjUHc3U3RtcEFpK1VCSGd0UE13OTVydFpLQnpjSmxXdHdDTHBDVjJLRWMrd1lFbEJ2YmI1WnZVUENEMSt2RHZxQkpJMG9HVFlva3dlNWQwOUVzNE1KcERjVStuWlI3U3QyV2tJQUE9IiwiQXV0aEluZm8iOiJ7XCJDSVwiOlwiYjJDdWhwWmFtY1doK05mMDJaaWZCVmVZS1BlVXY5Tjd6YitOSVk2aWtNNUd0VGxxMzYxZ0h1S0Z1WFBtek5YbGd3K205b3JjVWtYSlJGa2NIUGpSMG14a0pkWkxwa0tpWk5yN3haandOREk9XCIsXCJDYWxsZXJcIjpcInQ1elduMUVBOHVmcUVoc3lPWVVJck0yZVBiaTk3RzNuaUtPTU9ka3RIbXc9XCIsXCJFeHBpcmVUaW1lXCI6XCIyMDIzLTEwLTI4VDAyOjAxOjQ0WlwiLFwiTWVkaWFJZFwiOlwiZTY5NTg5MjAwZjNmNzFlZTk4YWM2NzMyYjc4ZTAxMDJcIixcIlBsYXlEb21haW5cIjpcInYzMC41MWN0by5jb21cIixcIlNpZ25hdHVyZVwiOlwiSWhUL2pGUmxuTlZKeEI4UXY1UU9YYlBlNjA4PVwifSIsIlZpZGVvTWV0YSI6eyJTdGF0dXMiOiJOb3JtYWwiLCJWaWRlb0lkIjoiZTY5NTg5MjAwZjNmNzFlZTk4YWM2NzMyYjc4ZTAxMDIiLCJUaXRsZSI6IjAxLkZyaWRh5LuL57uNIiwiQ292ZXJVUkwiOiJodHRwczovL3YzMC41MWN0by5jb20vZTY5NTg5MjAwZjNmNzFlZTk4YWM2NzMyYjc4ZTAxMDIvc25hcHNob3RzL2Q5NjE5N2U2M2RkYzQyM2U5YTM4ZTgxY2JlY2NmMTM5LTAwMDA1LmpwZyIsIkR1cmF0aW9uIjo3OTIuOTA3fSwiQWNjZXNzS2V5SWQiOiJTVFMuTlRDdjgyMzU5VGp3NjE0c0MyaWNNZlhwcSIsIlBsYXlEb21haW4iOiJ2MzAuNTFjdG8uY29tIiwiQWNjZXNzS2V5U2VjcmV0IjoiR0RjeTdvRVQzck5pZ0VORUg0SG1qTUJoYXk1QUJDQnNub3VnQ1NENWZpd1oiLCJSZWdpb24iOiJjbi1zaGFuZ2hhaSIsIkN1c3RvbWVySWQiOjExMzI2MzE0MjEwNTc3Nzd9";
        String videoId = "e69589200f3f71ee98ac6732b78e0102";
        String aliUrl = "https://vod.cn-shanghai.aliyuncs.com/?";
        PlayInfo playerInfo = getVodPlayerInfo(aliUrl, encryptRand, playAuth, videoId, "");
    }

    @SneakyThrows
    public static PlayInfo getVodPlayerInfo(String aliUrl, String rand, String aliPlayAuth, String videoId, String formats) {
        String playAuthStr = EncryptUtils.decodePlayAuth(aliPlayAuth);
        PlayAuth playAuth = PlayAuth.from(playAuthStr);
        Map<String, String> publicParam = new HashMap<>();
        Map<String, String> privateParam = new HashMap<>();
        publicParam.put("AccessKeyId", playAuth.getAccessKeyId());
        publicParam.put("Timestamp", generateTimestamp());
        publicParam.put("SignatureMethod", "HMAC-SHA1");
        publicParam.put("SignatureVersion", "1.0");
        publicParam.put("SignatureNonce", generateRandom());
        publicParam.put("Format", "JSON");
        publicParam.put("Channel", "HTML5");
        publicParam.put("StreamType", "video");
        if (StrUtil.isNotBlank(rand))
            publicParam.put("Rand", rand);
        if (StrUtil.isNotBlank(formats))
            publicParam.put("Formats", formats);
        else
            publicParam.put("Formats", "");
        publicParam.put("Version", "2017-03-21");

        privateParam.put("Action", "GetPlayInfo");
        privateParam.put("AuthInfo", playAuth.getAuthInfo());
        privateParam.put("AuthTimeout", "7200");
        privateParam.put("Definition", "240");
        privateParam.put("PlayConfig", "{}");
//        privateParam.put("PlayConfig", "{\"EncryptType\":\"Unencrypted\"}");
        privateParam.put("ReAuthInfo", "{}");
        privateParam.put("SecurityToken", playAuth.getSecurityToken());
        privateParam.put("VideoId", videoId);
        List<String> allParams = getAllParams(publicParam, privateParam);
        String cqs = getCQS(allParams);
        String stringToSign = "GET" + "&" + percentEncode("/") + "&" + percentEncode(cqs);
        byte[] bytes = hmacSHA1Signature(playAuth.getAccessKeySecret(), stringToSign);
        String signature = newStringByBase64(bytes);
        String queryString = cqs + "&Signature=" + percentEncode(signature);
        String playInfoRequestUrl =  aliUrl + queryString;
        String responseStr = HttpUtil.get(playInfoRequestUrl);
        logger.debug("Aliyun API request result:" + responseStr);
        JSONObject response = JSON.parseObject(responseStr);
        if (response.getString("Code") != null) throw new RuntimeException("获取媒体信息失败:");
        return JSON.parseObject(responseStr, PlayInfo.class);
    }

    /*特殊字符替换为转义字符*/
    public static String percentEncode(String value) {
        try {
            String urlEncodeOriginalStr = URLEncoder.encode(value, "UTF-8");
            String plusReplaced = urlEncodeOriginalStr.replace("+", "%20");
            String starReplaced = plusReplaced.replace("*", "%2A");
            return starReplaced.replace("%7E", "~");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return value;
    }

    /*对所有参数名称和参数值做URL编码*/
    public static List<String> getAllParams(Map<String, String> publicParams, Map<String, String> privateParams) {
        List<String> encodeParams = new ArrayList<String>();
        if (publicParams != null) {
            for (String key : publicParams.keySet()) {
                String value = publicParams.get(key);
                //将参数和值都urlEncode一下。
                String encodeKey = percentEncode(key);
                String encodeVal = percentEncode(value);
                encodeParams.add(encodeKey + "=" + encodeVal);
            }
        }
        if (privateParams != null) {
            for (String key : privateParams.keySet()) {
                String value = privateParams.get(key);
                //将参数和值都urlEncode一下。
                String encodeKey = percentEncode(key);
                String encodeVal = percentEncode(value);
                encodeParams.add(encodeKey + "=" + encodeVal);
            }
        }
        return encodeParams;
    }

    /*获取 CanonicalizedQueryString*/
    public static String getCQS(List<String> allParams) {
        ParamsComparator paramsComparator = new ParamsComparator();
        allParams.sort(paramsComparator);
        StringBuilder cqString = new StringBuilder();
        for (int i = 0; i < allParams.size(); i++) {
            cqString.append(allParams.get(i));
            if (i != allParams.size() - 1) {
                cqString.append("&");
            }
        }
        return cqString.toString();
    }

    /*字符串参数比较器，按字母序升序*/
    public static class ParamsComparator implements Comparator<String> {
        @Override
        public int compare(String lhs, String rhs) {
            return lhs.compareTo(rhs);
        }
    }

    public static byte[] hmacSHA1Signature(String accessKeySecret, String stringToSign) {
        try {
            String key = accessKeySecret + "&";
            try {
                SecretKeySpec signKey = new SecretKeySpec(key.getBytes(), "HmacSHA1");
                Mac mac = Mac.getInstance("HmacSHA1");
                mac.init(signKey);
                return mac.doFinal(stringToSign.getBytes());
            } catch (Exception e) {
                throw new SignatureException("Failed to generate HMAC : " + e.getMessage());
            }
        } catch (SignatureException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String newStringByBase64(byte[] bytes) {
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        return Base64.encode(bytes);
    }

    /*生成当前UTC时间戳Time*/
    public static String generateTimestamp() {
        Date date = new Date(System.currentTimeMillis());
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        df.setTimeZone(new SimpleTimeZone(0, "GMT"));
        return df.format(date);
    }

    public static String generateRandom() {
        return UUID.randomUUID().toString();
    }

}
